﻿using System.Diagnostics;
using System.Text.Json;
using Core.Attributes;
using Core.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Serilog.Context;

namespace BP.Plugin.Monitor.Infrastructure.Middlewares;

public class ResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ResponseLoggingMiddleware> _logger;

    public ResponseLoggingMiddleware(
        RequestDelegate next,
        ILogger<ResponseLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        if (HasNoLoggingAttribute(context)) 
        {
            await _next(context); // 不记录日志
            return;
        }

        var traceId = Activity.Current?.TraceId.ToString() ?? string.Empty;
        var stopwatch = Stopwatch.StartNew();

        // ---- GET 参数 ----
        string requestQueryParams = JsonSerializer.Serialize(
            context.Request.Query.ToDictionary(q => q.Key, q => q.Value.ToString()));

        // ---- 读取请求体 ----
        string? requestBody = await ReadRequestBody(context);
        var requestHeaders = JsonSerializer.Serialize(
            context.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()));

        // 替换响应流
        var originalBodyStream = context.Response.Body;
        await using var responseBody = new MemoryStream();
        context.Response.Body = responseBody;

        try
        {
            await _next(context); // 执行下一个中间件

            stopwatch.Stop();
            var elapsed = stopwatch.ElapsedMilliseconds;

            // ---- 响应头 ----
            var responseHeaders = JsonSerializer.Serialize(
                context.Response.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()));

            // ---- 响应体 ----
            string? responseText = await ReadResponseBody(context);
            var responseBodyJson = JsonSerializer.Serialize(responseText);

            // ---- 写入日志（成功）----
            using (LogContext.PushProperty("TraceId", traceId))
            using (LogContext.PushProperty("ElapsedMilliseconds", elapsed))
            using (LogContext.PushProperty("RequestHeaders", requestHeaders))
            using (LogContext.PushProperty("RequestBody", requestBody))
            using (LogContext.PushProperty("RequestQueryParams", requestQueryParams))
            using (LogContext.PushProperty("ResponseHeaders", responseHeaders))
            using (LogContext.PushProperty("StatusCode", 200))
            using (LogContext.PushProperty("ResponseBody", responseBodyJson))
            {
                _logger.LogInformation(
                    "HTTP {Method} {Path} 执行成功！耗时：{Elapsed}ms",
                    context.Request.Method,
                    context.Request.Path,
                    elapsed);
            }
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            var result = await Result.SucceedAsync();
            var elapsed = stopwatch.ElapsedMilliseconds;

            // ---- 响应头（异常时可能部分写入）----
            var responseHeaders = JsonSerializer.Serialize(
                context.Response.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()));

            result.Success = false;
            result.Code = StatusCodes.Status400BadRequest;
            result.Message = ex.Message;
            var responseBodyJson = JsonSerializer.Serialize(result);

            using (LogContext.PushProperty("TraceId", traceId))
            using (LogContext.PushProperty("ElapsedMilliseconds", elapsed))
            using (LogContext.PushProperty("RequestHeaders", requestHeaders))
            using (LogContext.PushProperty("RequestBody", requestBody))
            using (LogContext.PushProperty("RequestQueryParams", requestQueryParams))
            using (LogContext.PushProperty("ResponseHeaders", responseHeaders))
            using (LogContext.PushProperty("StatusCode", 200))
            using (LogContext.PushProperty("ResponseBody", responseBodyJson))
            {
                _logger.LogError(ex,
                    "HTTP {Method} {Path} 执行失败！耗时：{Elapsed}ms，错误：{Message}",
                    context.Request.Method,
                    context.Request.Path,
                    elapsed,
                    ex.Message);
            }

            throw;
        }
        finally
        {
            // ---- 写回响应流 ----
            responseBody.Seek(0, SeekOrigin.Begin);
            await responseBody.CopyToAsync(originalBodyStream);
            context.Response.Body = originalBodyStream;
        }
    }

    // ------------------------ Request Body ------------------------

    private async Task<string?> ReadRequestBody(HttpContext context)
    {
        context.Request.EnableBuffering(); // 允许重复读取 request body

        if (!IsText(context.Request.ContentType))
            return null;

        context.Request.Body.Seek(0, SeekOrigin.Begin);
        var text = await new StreamReader(context.Request.Body).ReadToEndAsync();
        context.Request.Body.Seek(0, SeekOrigin.Begin);

        return text;
    }

    // ------------------------ Response Body ------------------------

    private async Task<string?> ReadResponseBody(HttpContext context)
    {
        if (!IsText(context.Response.ContentType))
            return null;

        context.Response.Body.Seek(0, SeekOrigin.Begin);
        var text = await new StreamReader(context.Response.Body).ReadToEndAsync();
        context.Response.Body.Seek(0, SeekOrigin.Begin);

        return text;
    }

    // ------------------------ ContentType 判断 ------------------------

    private bool IsText(string? contentType)
    {
        if (string.IsNullOrWhiteSpace(contentType))
            return false;

        return contentType.Contains("json", StringComparison.OrdinalIgnoreCase)
            || contentType.Contains("text", StringComparison.OrdinalIgnoreCase)
            || contentType.Contains("xml", StringComparison.OrdinalIgnoreCase)
            || contentType.Contains("html", StringComparison.OrdinalIgnoreCase)
            || contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)
            || contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase);
    }

    private bool HasNoLoggingAttribute(HttpContext context)
    {
        var endpoint = context.GetEndpoint();
        if (endpoint is null)
            return false;

        return endpoint.Metadata.OfType<NoLoggingAttribute>().Any();
    }
}





